/*
    模拟实现http协议：http协议请求和响应，以及http服务端处理报文的接口，client直接使用浏览器（可以发送现成的http请求）
    http协议不需要处理接收报文中的业务，只负责报文的序列化和反序列化
*/
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <sstream>
#include <utility>
#include <memory>
#include <functional>
#include "log.hpp"

namespace http_ns
{
    pthread_mutex_t mutex; // 此锁仅仅是测试使用，为了打印的更清晰

    class HttpRequest;
    class HttpResponse;
    // 业务处理函数类型（回调函数，因为http不做业务处理），根据client给哪个服务提交的参数去做相应的业务处理（也就是拿到请求做出应答）
    using business_t = std::function<std::shared_ptr<http_ns::HttpResponse>(std::shared_ptr<http_ns::HttpRequest> &)>;

    static const std::string lineseq = "\r\n";        // 协议中行之间的间隔
    static const std::string seq = " ";               // 请求行中字段的间隔
    static const std::string headseq = ": ";          // 报头kv之间的间隔
    static const std::string webroot = "wwwroot";     // web根目录
    static const std::string homepage = "index.html"; // 默认首页
    static const std::string filesuffixseq = ".";     // 文件后缀间隔符
    static const std::string parameterseq = "?";      // 请求中使用get方法提参数时，uri中携带参数，uri与参数的间隔符号

    static const std::string statuscodetodescpath = "./statuscodetodesc.txt"; // 状态码转状态码描述文件路径
    static const std::string suffixtotypepath = "./suffixtotype.txt";         // 资源文件后缀转文件类型文件路径
    static const std::string errorpath = "wwwroot/404.html";                  // 读取错误时候返回的资源文件路径

    // http服务端版本，1.0版本不支持持久连接，也就是每次连接仅处理一次请求，1.1支持持久连接，每次连接可以处理多次请求
    // 以前写的网络计算器就是1.1版本
    static const std::string httpserverversion = "HTTP\1.0";

    class HttpRequest
    {
        /*
            http请求格式：
            请求行：方法+空格+uri+空格+http版本+\r\n
            报头：key:+空格+value+\r\n
            空行：\r\n
            有效载荷：结构化数据
        */
    private:
        std::string getOneLine(std::string &request)
        {
            size_t pos = request.find(lineseq);
            if (pos == std::string::npos) // 读取错误
                return string();
            std::string linestr = request.substr(0, pos);
            request.erase(0, pos + lineseq.size());
            return linestr.empty() ? lineseq : linestr; // 读到了空行返回lineseq，非空则返回读到的行
        }

        void parseReqLine()
        {
            if (_reqline.empty())
            {
                LOG(ERROR, "parseReqline fail to do, reqline is empty\n");
                return;
            }
            std::stringstream ss(_reqline);
            ss >> _method >> _uri >> _version; // 字符串流，以空格为间隔符，将字符串写入到变量中

            // 从_uri中提取请求参数，并将参数从uri中去除
            //  /login?user=zhangsan&password=1234
            // 判断是否是get方法，因为get方法以url的方式提参（参数以？作为间隔符拼接在uri的后面），post方法以有效载荷的方式提参
            // 如果是post方法，参数直接就在_reqtext中，所以暂时不用处理
            if (strcasecmp(_method.c_str(), "get") == 0)
            {
                size_t pos = _uri.find(parameterseq);
                if (pos != std::string::npos) // 如果找到npos，不能代表出错，因为可能就没有提参
                {
                    _args = _uri.substr(pos + parameterseq.size());
                    _uri.resize(pos);
                }
            }

            _path += _uri; // 构建完整的资源相对路径

            // 判断请求的是不是/，如果是加上起始页，每个目录下都有一个起始页码
            if (_uri[_uri.size() - 1] == '/')
                _path += homepage;

            // 设置请求资源文件后缀
            size_t pos = _path.rfind(filesuffixseq);
            if (pos == std::string::npos)
            {
                LOG(ERROR, "parseReqline fail to do, filesuffixseq fail to find\n");
                _suffix = ".unknown"; // 设置成.unknown，后面响应的时候根据这个后缀，返回相应的信息
                return;
            }
            _suffix = _path.substr(pos);
        }

        void parseReqHead()
        {
            for (auto &s : _reqheads)
            {
                size_t pos = s.find(headseq);
                if (pos == std::string::npos)
                {
                    LOG(ERROR, "parseReqHead failed to do\n");
                    continue;
                }
                _reqheadskv.insert(std::make_pair(s.substr(0, pos), s.substr(pos + headseq.size())));
            }
        }

    public:
        HttpRequest() : _blankline(lineseq), _path(webroot)
        {
        }

        void serialize() // 因为客户端不用写，所有序列化也就不是实现了
        {
        }

        void derialize(std::string &request)
        {
            // 将数据报按行拆分
            _reqline = getOneLine(request); // 获取请行
            while (true)
            {
                // 获取报头
                std::string line = getOneLine(request);
                if (line == lineseq)
                {
                    _reqtext = request; // 剩余的一定是有效载荷(可能是空或者不空)
                    break;
                }
                else if (line.empty())
                    break;
                else
                    _reqheads.emplace_back(line);
            }

            // 将上面拆分后的行进一步拆分
            parseReqLine(); // 解析请求行
            parseReqHead(); // 解析请求报头
        }

        std::string method()
        {
            return _method;
        }

        std::string text()
        {
            return _reqtext;
        }

        std::string path()
        {
            return _path;
        }

        std::string suffix()
        {
            return _suffix;
        }

        std::string args()
        {
            return _args;
        }

        bool IsSubmitParam()
        {
            return (!_args.empty() || !_reqtext.empty());
        }

        void printRequest()
        {
            pthread_mutex_lock(&mutex);
            std::cout << "**********Request************" << std::endl;
            std::cout << _reqline << std::endl;
            for (auto &s : _reqheads)
            {
                std::cout << s << std::endl;
            }
            std::cout << _blankline << std::endl;
            std::cout << _reqtext << std::endl;

            std::cout << "---------------------------" << std::endl;

            std::cout << "method:" << _method << std::endl;
            std::cout << "uri:" << _uri << std::endl;
            std::cout << "path:" << _path << std::endl;
            std::cout << "args:" << _args << std::endl;
            std::cout << "version:" << _version << std::endl;
            for (auto &e : _reqheadskv)
            {
                std::cout << e.first << ": " << e.second << std::endl;
            }
            std::cout << _blankline << std::endl;
            std::cout << _reqtext << std::endl;
            std::cout << "***************************" << std::endl;
            pthread_mutex_unlock(&mutex);
        }

    private:
        // 协议原始数据
        std::string _reqline;               // 请求行
        std::vector<std::string> _reqheads; // 请求报头
        std::string _blankline;             // 空行
        std::string _reqtext;               // 有效载荷

        // 解析后的数据（对上面原始数据的进一步解析）
        std::string _method;                                      // 请求方法
        std::string _uri;                                         // 统一资源定位符
        std::string _args;                                        // 请求中携带的参数
        std::string _version;                                     // client端的http版本
        std::unordered_map<std::string, std::string> _reqheadskv; // 请求报头的kv形式

        std::string _path;   // 要请求的资源路径
        std::string _suffix; // 请求的资源文件后缀，因为响应报头中要有Content-type键值，而它是根据请求的资源文件后缀来填写的
    };

    class HttpResponse
    {
        /*
            http响应格式：
            状态行：http版本+空格+状态码+空格+状态码描述+\r\n
            报头：key:+空格+value+\r\n
            空行：\r\n
            有效载荷：结构化数据
        */
    public:
        HttpResponse() : _blankline(lineseq), _version(httpserverversion)
        {
        }

        void addStatusLine(int statuscode, std::string &statusdesc) // 添加状态码和状态描述和http服务端版本
        {
            _statuscode = statuscode;
            _statusdesc = statusdesc;
        }

        void addHead(std::string k, std::string v) // 添加报头
        {
            _resheadskv[k] = v;
        }

        void addText(std::string &text) // 添加有效载荷
        {
            _restext = text;
        }

        void derialize() // 因为不需要实现客户端，所有不用实现反序列化
        {
        }

        void serialize(std::string &response)
        {
            // 构建状态行
            _statusline = _version + seq + to_string(_statuscode) + seq + _statusdesc + lineseq;
            // 构建相应报头
            for (auto &e : _resheadskv)
            {
                _resheads.emplace_back(e.first + headseq + e.second + lineseq);
            }
            // 序列化
            response += _statusline;
            for (auto &s : _resheads)
            {
                response += s;
            }
            response += _blankline;
            response += _restext;
        }

        void printResponse()
        {
            pthread_mutex_lock(&mutex);
            std::cout << "**********Response**********" << std::endl;
            std::cout << "version:" << _version << std::endl;
            std::cout << "statuscode:" << _statuscode << std::endl;
            std::cout << "statusdesc:" << _statusdesc << std::endl;
            std::cout << "restext:" << _restext << std::endl;
            std::cout << "***************************" << std::endl;
            pthread_mutex_unlock(&mutex);
        }

        ~HttpResponse()
        {
        }

    private:
        // 原始协议数据
        std::string _statusline;            // 状态行
        std::vector<std::string> _resheads; // 响应报头
        std::string _blankline;             // 空行
        std::string _restext;               // 有效载荷

        // 解析后的数据
        std::string _version;                                     // 版本号
        int _statuscode;                                          // 状态码
        std::string _statusdesc;                                  // 状态码描述
        std::unordered_map<std::string, std::string> _resheadskv; // 请求报头的kv形式
    };

    class Factory
    {
        /*
            加工成，生产http请求和响应
        */
    public:
        static shared_ptr<http_ns::HttpRequest> buildHttpRequest()
        {
            return make_shared<http_ns::HttpRequest>();
        }

        static shared_ptr<http_ns::HttpResponse> buildHttpResponse()
        {
            return make_shared<http_ns::HttpResponse>();
        }
    };

    class HttpServer // http服务
    {
    private:
        std::string readFileContext(std::string path, int &filesize)
        {
            std::ifstream in(path, std::ios::binary); // http是超文本传输协议，所有可能有各种文件格式，所以以二进制的形式读取
            if (!in.is_open())
            {
                LOG(FATAL, "readFileContext:open fail to do\n");
                filesize = 0;
                return std::string();
            }
            // 计算文件的大小
            in.seekg(0, in.end);   // 0表示偏移量，in.end，将文件指针定位到最后一个位置
            filesize = in.tellg(); // 返回文件指针的位置，也代表文件的大小（字节）
            in.seekg(0, in.beg);

            std::string context;
            context.resize(filesize);
            in.read((char *)context.c_str(), filesize);
            in.close();
            return context;
        }

        void initStatusDescTable()
        {
            std::ifstream in(_statusdescpath);
            if (!in.is_open())
            {
                LOG(FATAL, "initStatusDescTable:open fail to do\n");
                return;
            }
            while (true)
            {
                std::string oneline;
                std::getline(in, oneline);
                if (oneline.empty())
                    break;
                std::cout << "statusdesc oneline:" << oneline << std::endl;
                size_t pos = oneline.find(" ");
                if (pos == std::string::npos)
                {
                    LOG(ERROR, "initStatusDescTable:oneline fail to find space\n");
                    continue;
                }
                _statusdesctable.insert(std::make_pair(std::stoi(oneline.substr(0, pos)), oneline.substr(pos + 1)));
            }
        }

        void initSuffixTypeTable()
        {
            std::ifstream in(_suffixtypepath);
            if (!in.is_open())
            {
                LOG(FATAL, "initSuffixTypeTable:open fail to do\n");
                return;
            }
            while (true)
            {
                std::string oneline;
                std::getline(in, oneline);
                if (oneline.empty())
                    break;
                std::cout << "suffixtype oneline:" << oneline << std::endl;
                size_t pos = oneline.find(" ");
                if (pos == std::string::npos)
                    continue;
                _suffixtypetable[oneline.substr(0, pos)] = oneline.substr(pos + 1);
            }
        }

    public:
        HttpServer() : _statusdescpath(statuscodetodescpath), _suffixtypepath(suffixtotypepath)
        {
            initStatusDescTable();
            initSuffixTypeTable();
        }

        void addBusinessHander(std::string businessname, business_t businesshander)
        {
            std::string absolutepath = webroot + businessname; // 构建绝对路径，因为查找要执行的业务用的是path
            cout << "absolutepath:" << absolutepath << endl;
            _bussinesshander[absolutepath] = businesshander;
        }

        std::string server(std::string request)
        {
#ifdef TEST
            std::cout << request << std::endl;
            std::string response = "HTTP/1.0 200 OK\r\n"; // 404 NOT Found
            response += "\r\n";
            response += "<html><body><h1>hello world, hello bite!</h1></body></html>";
            return response;
#else
            // 一、处理请求
            shared_ptr<http_ns::HttpRequest> req = Factory::buildHttpRequest();
            req->derialize(request); // 反序列化请求
            req->printRequest();

            // 二、做出响应
            /*
            下面这部分代码是之前的测试代码，并且下面代码功能也可以通过功能路由的方式实现

            // if (req->path() == "wwwroot/redir") // 重定向(这种方式的重定向，是用户请求之后直接应答，让用户再次向新的域名发起请求)
            // {
            //     std::string newurl = "https://www.baidu.com/"; // 重定向到百度首页
            //     statuscode = 301;                              // 跳转到新域名，更新旧的域名（如保存的标签）
            //     // statuscode = 302; //跳转到新域名，不更新域名
            //     res->addStatusLine(statuscode, _statusdesctable[301]);
            //     res->addHead("location", newurl);
            // }
            // else if (req->path() == "wwwroot/login") // 测试get方法和post方法提交参数的区别
            // {
            //     // 当我们请求提交参数的时候一般使用表单的方式（输入框，2.html里），所以post和get方法一般就是当使用表单的时候使用
            //     // 在2.html中测试get方法和post方法
            //     //  #如果form表单里没有写方法，默认是get方法
            //     //  使用get方法提参，参数以url的方式提交（拼接咋uri后面用？作为分隔符，所以序列化的时候要注意拆分）
            //     //  wwwroot/login可以是一个网页，也可是一个服务，我只要检测到你要提参到这里，我就可以在内部做相应的工作
            //     //  post也可以提交参数，和get的区别就是post以有效载荷的方式提惨
            //     //  post比get私密性更好，不代表更安全，在抓包看来两个都是明文
            //     //   1、设置状态行
            //     statuscode = 404;
            //     res->addStatusLine(statuscode, _statusdesctable[statuscode]);

            //     // 2、重新添加有效载荷
            //     int size = 0;
            //     std::string restext = readFileContext(errorpath, size); // 读取失败后要返回的资源
            //     res->addText(restext);

            //     // 3、添加报头
            //     res->addHead("Content-Length", std::to_string(size));          // 有效载荷长度
            //     res->addHead("Content-Type", _suffixtypetable[req->suffix()]); // 有效载荷类型
            // }
            */
            std::string response;
            if (req->IsSubmitParam())
            {
                // 动态请求（根据提参给哪个服务完成业务处理）
                shared_ptr<http_ns::HttpResponse> res = _bussinesshander[req->path()](req);
                res->serialize(response);
            }
            else
            {
                // 下面全是静态请求（没有提参）
                shared_ptr<http_ns::HttpResponse> res = Factory::buildHttpResponse();
                int size;       // 有效载荷大小
                int statuscode; // 状态码
                std::string restext = readFileContext(req->path(), size);
                if (restext.empty()) // 获取资源失败
                {
                    // 1、设置状态行
                    statuscode = 404;
                    res->addStatusLine(statuscode, _statusdesctable[statuscode]);

                    // 2、重新添加有效载荷
                    restext = readFileContext(errorpath, size); // 读取失败后要返回的资源
                    res->addText(restext);

                    // 3、添加报头
                    res->addHead("Content-Length", std::to_string(size));          // 有效载荷长度
                    res->addHead("Content-Type", _suffixtypetable[req->suffix()]); // 有效载荷类型
                }
                else
                {
                    // 1、设置状态行
                    statuscode = 200;
                    res->addStatusLine(statuscode, _statusdesctable[statuscode]);

                    // 2、添加报头
                    res->addHead("Content-Length", std::to_string(size));          // 有效载荷长度
                    res->addHead("Content-Type", _suffixtypetable[req->suffix()]); // 有效载荷类型

                    // 3、添加有效载荷
                    res->addText(restext);
                }
                res->serialize(response);
            }
            return response;
#endif
        }

        ~HttpServer()
        {
        }

    private:
        std::unordered_map<int, std::string> _statusdesctable;         // 状态码对应的状态码描述表，http协议已经给我们规定好了不同文件后缀对应的文件类型
        std::unordered_map<std::string, std::string> _suffixtypetable; // 资源文件后缀对应的文件类型表
        std::string _statusdescpath;                                   // 状态描述文件路径
        std::string _suffixtypepath;                                   // 后缀类型文件路径

        std::unordered_map<std::string, business_t> _bussinesshander; // 根据给哪个服务提交的请求，执行相应的业务处理
    };
}